本文章將提及以下內容
我們將component比擬成電梯,因此取名叫做Elevator的component,最上層是App.js
、最下層是Elevator3.js
觀看以下import的階層關係
App import Elevator1
Elevator1 import Elevator2
Elevator2 import Elevator3
範例如下
//App.js檔案
import React, { useState } from 'react'
import Elevator1 from './Elevator1'
const App = () => {
const [user, setUser] = useState("Tim");
return (
<>
<Elevator1 user={user} />
</>
);
};
export default App;
//Elevator1.js檔案
import React from 'react'
import Elevator2 from './Elevator2'
const Elevator1 = ({ user }) => {//為了向下傳遞接收上層傳來的props
return (
<>
//為了向下傳遞必須撰寫props
<Elevator2 user={user} />
</>
)
}
export default Elevator1
//Elevator2.js檔案
import React from 'react'
import Elevator3 from './Elevator3'
const Elevator2 = ({ user }) => {//為了向下傳遞接收上層傳來的props
return (
<>
//為了向下傳遞必須撰寫props
<Elevator3 user={user} />
</>
)
}
export default Elevator2
//Elevator3.js檔案
import React from 'react'
const Elevator3 = ({ user }) => {
return (
<>
{user}
</>
)
}
export default Elevator3
如果想要將App的值傳遞到Elevator3的話就得在每一層藉由props傳遞,其中Elevator2和Elevator1都沒有用到這個變數卻還是得宣告在props
,因此會出現(Prop Drilling)過度傳遞問題。
為了避免過度傳遞,react官方提出可以使用context(備註)來解決上述問題
備註:如果你去查context的中文,得到的翻譯會是上下文、來龍去脈、背景、我們可以比喻成我們創造了一個背景空間,提供橋梁使得各個元件來這裡就可以得到共用值。
使用context我們關注以下三點
另外useContext只是Context.Consumer的語法糖,稍後會提到解釋。
首先我們先引入createContext,他是用來製造context的函式。
這邊我將檔案給分離出來命名成createUseContext.js
程式碼如下
import React, { createContext } from 'react'
export const UserContext = createContext();
第二步引入剛剛所創建的檔案後,將準備值提供給Provider。
使用方式是先將剛剛所建立的context帶入到jsx裡面,透過物件取值提取Provider並設定props屬性為value,而value的值是你要共用的值,以下面範例所指的共用的值就是user這個變數。
Provider需要放在component較上層的地方,以這個範例來說App是Elevator1、Elevator2、Elevator3的上層。
在App.js引入這支檔案
import React, { useState } from "react";
import { UserContext } from './createUserContext';
import Elevator1 from './Elevator1'
const App = () => {
const [user, setUser] = useState("Tim");
return (
<>
<UserContext.Provider value={user}>
<Elevator1 />
</UserContext.Provider>
</>
);
}
export default App
最後要在使用值的地方藉由Consumer來提取值,記得先引入剛剛所建立的UserContext,並且使用context的consumer來提取共用的值。範例如下
//在Elevator3.js
import React from 'react'
import { UserContext } from './createUserContext'
const Elevator3 = () => {
return (
<>
<UserContext.Consumer>
{value => <h1>{value}</h1>}
</UserContext.Consumer>
</>
)
}
export default Elevator3
如期我們就可以在Elevator3提取到value。
由於要提取共用的值需要撰寫<UserContext.Consumer>
和{value => <h1>{value}</h1>}
的語法過於冗長,因此使用useContext可以簡化。
使用的方式將剛剛所建立的context整個帶入到useContext函式裡面作為參數即可,useContext回傳的值就會是在Provider所建立的值。
範例如下
//在Elevator3.js
import React, { useContext } from 'react'
import { UserContext } from './createUserContext'
const Elevator3 = () => {
const name = useContext(UserContext);
return (
<>
<h1>{name}</h1>
</>
)
}
export default Elevator3
官方提到以下幾個重點
以先前的範例如果在Provider撰寫如下的方式,最後consumer只會取得比較靠近的provider的值。
範例如下
<UserContext.Provider value={user}>
<UserContext.Provider value={"test"}>
<Elevator1 />
</UserContext.Provider >
</UserContext.Provider>
當context的state改變的時候底下的consume將全部重新渲染
換句話說,如果整個Application的state只使用一個context的話,那麼將會造成大量的重新渲染。
長久累積將會產生效能問題。
既然官方提出consumer這個hook一定有它的用處,
面對不常變動的state就可以考慮使用useContext。
另外面對重新渲染的問題也可以透過宣告不同的context來解決或是搭配useMemo來做最佳化效能處理。
官方issue有提出一些方式可以參考看看或是也可以選擇使用知名第三方套件redux的解決方式。